﻿using DimensionsHelper.Common.Contracts.Excel;
using DimensionsHelper.Common.Contracts.Excel.Options;
using DimensionsHelper.Common.Excel;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using FontType = DimensionsHelper.Common.Contracts.Excel.FontType;

namespace DimensionsHelper.Services.Excel.EPPlus;

public class ExcelRangeOperator : IExcelRangeOperator
{
    private ExcelRangeOperator(IExcelWorksheetOperator worksheet, ExcelRangeBase range)
    {
        Worksheet = worksheet;
        Range = range;
    }

    private ExcelRangeBase Range { get; }

    internal static ExcelRangeOperator Create(IExcelWorksheetOperator worksheet, ExcelRangeBase range)
    {
        return new ExcelRangeOperator(worksheet, range);
    }

    public IExcelWorksheetOperator Worksheet { get; }

    public int StartRow => Range.Start.Row;

    public int EndRow => Range.End.Row;

    public int StartColumn => Range.Start.Column;

    public int EndColumn => Range.End.Column;

    public string Address => Range.Address;

    public string FullAddress => Range.FullAddress;

    public string FullAddressAbsolute => Range.FullAddressAbsolute;

    public object Value
    {
        get => Range.Value;
        set
        {
            Range.Value = value;
            SetRangeFontStyle(Worksheet.Workbook.GlobalFontStyleOptions);
        }
    }

    public string Text => Range.GetCellValue<string>() ?? string.Empty;

    public string NumberFormatLocal
    {
        get => Range.Style.Numberformat.Format;
        set => Range.Style.Numberformat.Format = value;
    }

    public bool WrapText
    {
        get => Range.Style.WrapText;
        set => Range.Style.WrapText = value;
    }


    public float FontSize
    {
        get => Range.Style.Font.Size;
        set => Range.Style.Font.Size = value;
    }


    public FontType FontStyle
    {
        get
        {
            var style = FontType.None;

            if (Range.Style.Font.Bold)
            {
                style |= FontType.Bold;
            }

            if (Range.Style.Font.Italic)
            {
                style |= FontType.Italic;
            }

            if (Range.Style.Font.UnderLine)
            {
                style |= FontType.Underline;
            }

            return style;

        }
        set
        {
            if ((value & FontType.Bold) > 0)
            {
                Range.Style.Font.Bold = true;
            }

            if ((value & FontType.Italic) > 0)
            {
                Range.Style.Font.Italic = true;
            }

            if ((value & FontType.Underline) > 0)
            {
                Range.Style.Font.UnderLine = true;
            }
        }
    }

    private static ExcelStyleColor GetColor(ExcelColor color)
    {
        return ExcelStyleColor.TryParse(color.Rgb, out var excelColor)
            ? excelColor
            : ExcelStyleColor.Empty;
    }

    public ExcelStyleColor FontColor
    {
        get => GetColor(Range.Style.Font.Color);
        set
        {
            if (value == ExcelStyleColor.Empty)
            {
                return;
            }

            Range.Style.Font.Color.SetColor(value.ToColor());
        }
    }

    public ExcelStyleColor BackgroundColor
    {
        get => GetColor(Range.Style.Fill.BackgroundColor);

        set
        {
            if (value == ExcelStyleColor.Empty && value != ExcelStyleColors.White)
            {
                return;
            }

            Range.Style.Fill.SetBackground(value.ToColor());
        }
    }


    private static ExcelUnderLineType OperatorUnderLineTypeToExcelType(UnderLineType underLineType)
    {
        return underLineType switch
        {
            UnderLineType.None => ExcelUnderLineType.None,
            UnderLineType.Single => ExcelUnderLineType.Single,
            UnderLineType.Double => ExcelUnderLineType.Double,
            UnderLineType.SingleAccounting => ExcelUnderLineType.SingleAccounting,
            UnderLineType.DoubleAccounting => ExcelUnderLineType.DoubleAccounting,
            _ => ExcelUnderLineType.Single,
        };
    }

    internal static ExcelVerticalAlignment OperatorVerticalAlignmentToExcelAlignment(ExcelVAlign alignment)
    {
        return alignment switch
        {
            ExcelVAlign.Top => ExcelVerticalAlignment.Top,
            ExcelVAlign.Center => ExcelVerticalAlignment.Center,
            ExcelVAlign.Bottom => ExcelVerticalAlignment.Bottom,
            ExcelVAlign.Distributed => ExcelVerticalAlignment.Distributed,
            ExcelVAlign.Justify => ExcelVerticalAlignment.Justify,
            _ => ExcelVerticalAlignment.Center,
        };
    }

    internal static ExcelHorizontalAlignment OperatorHorizontalAlignmentToExcelAlignment(ExcelHAlign alignment)
    {
        return alignment switch
        {
            ExcelHAlign.General => ExcelHorizontalAlignment.General,
            ExcelHAlign.Left => ExcelHorizontalAlignment.Left,
            ExcelHAlign.Center => ExcelHorizontalAlignment.Center,
            ExcelHAlign.CenterContinuous => ExcelHorizontalAlignment.CenterContinuous,
            ExcelHAlign.Right => ExcelHorizontalAlignment.Right,
            ExcelHAlign.Fill => ExcelHorizontalAlignment.Fill,
            ExcelHAlign.Distributed => ExcelHorizontalAlignment.Distributed,
            ExcelHAlign.Justify => ExcelHorizontalAlignment.Justify,
            _ => ExcelHorizontalAlignment.General,
        };
    }

    private static ExcelBorderStyle OperatorBorderStyleToExcelStyle(BorderStyle borderStyle)
    {
        return borderStyle switch
        {
            BorderStyle.None => ExcelBorderStyle.None,
            BorderStyle.Hair => ExcelBorderStyle.Hair,
            BorderStyle.Dotted => ExcelBorderStyle.Dotted,
            BorderStyle.DashDot => ExcelBorderStyle.DashDot,
            BorderStyle.Thin => ExcelBorderStyle.Thin,
            BorderStyle.DashDotDot => ExcelBorderStyle.DashDotDot,
            BorderStyle.Dashed => ExcelBorderStyle.Dashed,
            BorderStyle.MediumDashDotDot => ExcelBorderStyle.MediumDashDotDot,
            BorderStyle.MediumDashed => ExcelBorderStyle.MediumDashed,
            BorderStyle.MediumDashDot => ExcelBorderStyle.MediumDashDot,
            BorderStyle.Thick => ExcelBorderStyle.Thick,
            BorderStyle.Medium => ExcelBorderStyle.Medium,
            BorderStyle.Double => ExcelBorderStyle.Double,
            _ => ExcelBorderStyle.Thin,
        };
    }

    private static BorderStyle ExcelStyleToOperatorBorderStyle(ExcelBorderStyle borderStyle)
    {
        return borderStyle switch
        {
            ExcelBorderStyle.None => BorderStyle.None,
            ExcelBorderStyle.Hair => BorderStyle.Hair,
            ExcelBorderStyle.Dotted => BorderStyle.Dotted,
            ExcelBorderStyle.DashDot => BorderStyle.DashDot,
            ExcelBorderStyle.Thin => BorderStyle.Thin,
            ExcelBorderStyle.DashDotDot => BorderStyle.DashDotDot,
            ExcelBorderStyle.Dashed => BorderStyle.Dashed,
            ExcelBorderStyle.MediumDashDotDot => BorderStyle.MediumDashDotDot,
            ExcelBorderStyle.MediumDashed => BorderStyle.MediumDashed,
            ExcelBorderStyle.MediumDashDot => BorderStyle.MediumDashDot,
            ExcelBorderStyle.Thick => BorderStyle.Thick,
            ExcelBorderStyle.Medium => BorderStyle.Medium,
            ExcelBorderStyle.Double => BorderStyle.Double,
            _ => BorderStyle.Thin,
        };
    }

    public IExcelRangeOperator SetRangeFontStyle(FontStyleOptions param)
    {
        if (param.FontSize > 0)
        {
            Range.Style.Font.Size = param.FontSize;
        }

        if (!string.IsNullOrEmpty(param.FontFamily))
        {
            Range.Style.Font.Name = param.FontFamily;
        }

        if (param.FontColor != ExcelStyleColor.Empty)
        {
            Range.Style.Font.Color.SetColor(param.FontColor.ToColor());
        }

        if ((param.FontType & FontType.Bold) > 0)
        {
            Range.Style.Font.Bold = true;
        }

        if ((param.FontType & FontType.Italic) > 0)
        {
            Range.Style.Font.Italic = true;
        }

        if ((param.FontType & FontType.Underline) > 0)
        {
            Range.Style.Font.UnderLine = true;
            Range.Style.Font.UnderLineType = OperatorUnderLineTypeToExcelType(param.UnderLine);
        }

        return this;
    }

    public IExcelRangeOperator SetRangeBorderStyle(BorderPosition borderPosition, BorderStyle borderStyle)
    {
        var style = OperatorBorderStyleToExcelStyle(borderStyle);

        if ((borderPosition & BorderPosition.Around) != 0)
        {
            Range.Style.Border.BorderAround(style);
        }

        if ((borderPosition & BorderPosition.Horizontal) != 0)
        {
            for (var i = 0; i < Range.Rows; i++)
            {
                var row = Range.Worksheet
                    .Cells[StartRow + i, StartColumn, StartRow + i, EndColumn];

                // 优先添加上边框
                if ((borderPosition & BorderPosition.Top) != 0)
                {
                    row.Style.Border.Top.Style = style;
                }

                // 添加下边框时，在不添加上边框或最后一行时，添加下边框
                if ((borderPosition & BorderPosition.Bottom) != 0 && (
                        (borderPosition & BorderPosition.Top) == 0 || i == Range.Rows - 1))
                {
                    row.Style.Border.Bottom.Style = style;
                }
            }
        }

        if ((borderPosition & BorderPosition.Vertical) != 0)
        {
            for (var i = 0; i < Range.Columns; i++)
            {
                var col = Range.Worksheet
                    .Cells[StartRow, StartColumn + i, EndRow, StartColumn + i];

                // 优先添加左边框
                if ((borderPosition & BorderPosition.Left) != 0)
                {
                    col.Style.Border.Left.Style = style;
                }
                
                // 添加有边框时，在最后一列或不添加左边框时，才逐列添加。
                if ((borderPosition & BorderPosition.Right) != 0 && (
                        (borderPosition & BorderPosition.Left) == 0 || i == Range.Rows - 1))
                {
                    col.Style.Border.Right.Style = style;
                }
            }
        }

        return this;
    }

    public IExcelRangeOperator SetRangeBorderColor(BorderPosition borderPosition, BorderStyle borderStyle, ExcelStyleColor borderColor)
    {
        if (borderColor == ExcelStyleColor.Empty)
        {
            return this;
        }

        var drawingColor = borderColor.ToColor();
        var style = OperatorBorderStyleToExcelStyle(borderStyle);

        if ((borderPosition & BorderPosition.Around) != 0)
        {
            Range.Style.Border.BorderAround(style);

            if (style != ExcelBorderStyle.None)
            {
                Range.Style.Border.Top.Color.SetColor(drawingColor);
                Range.Style.Border.Left.Color.SetColor(drawingColor);
                Range.Style.Border.Right.Color.SetColor(drawingColor);
                Range.Style.Border.Bottom.Color.SetColor(drawingColor);
            }
        }

        if ((borderPosition & BorderPosition.Vertical) != 0 && Range.Columns > 1)
        {
            for (var i = 1; i < Range.Columns; i++)
            {
                var r = Range.Worksheet.Cells[StartRow, StartColumn + i, EndRow, StartColumn + i];
                r.Style.Border.Left.Style = style;

                if (style != ExcelBorderStyle.None)
                {
                    r.Style.Border.Left.Color.SetColor(drawingColor);
                }
            }
        }

        if ((borderPosition & BorderPosition.Horizontal) != 0 && Range.Rows > 1)
        {
            for (var i = 1; i < Range.Rows; i++)
            {
                var r = Range.Worksheet.Cells[StartRow + i, StartColumn, StartRow + i, EndColumn];
                r.Style.Border.Top.Style = style;

                if (style != ExcelBorderStyle.None)
                {
                    r.Style.Border.Top.Color.SetColor(drawingColor);
                }
            }
        }

        return this;
    }


    public IExcelRangeOperator SetRangeBorderColor(ExcelBorderLocation borderLocation, BorderStyle borderStyle, ExcelStyleColor borderColor)
    {
        ExcelBorderItem? borderItem = null;

        switch (borderLocation)
        {
            case ExcelBorderLocation.Left:
                borderItem = Range.Style.Border.Left;
                break;

            case ExcelBorderLocation.Top:
                borderItem = Range.Style.Border.Top;
                break;

            case ExcelBorderLocation.Right:
                borderItem = Range.Style.Border.Right;
                break;

            case ExcelBorderLocation.Bottom:
                borderItem = Range.Style.Border.Bottom;
                break;
        }

        if (borderItem == null)
        {
            return this;
        }

        if (borderStyle == BorderStyle.None)
        {
            borderItem.Style = ExcelBorderStyle.None;
        }
        else
        {
            borderItem.Style = OperatorBorderStyleToExcelStyle(borderStyle);
            borderItem.Color.SetColor(borderColor.ToColor());
        }

        return this;
    }


    public ExcelBorderState GetRangeBorderState(ExcelBorderLocation borderLocation)
    {

        var style = BorderStyle.None;
        var indexed = 0;
        string? rgb = null;
        int? theme = null;

        ExcelBorderItem? borderItem = null;

        switch (borderLocation)
        {
            case ExcelBorderLocation.Left:
                borderItem = Range.Style.Border.Left;
                break;

            case ExcelBorderLocation.Top:
                borderItem = Range.Style.Border.Top;
                break;

            case ExcelBorderLocation.Right:
                borderItem= Range.Style.Border.Right;
                break;

            case ExcelBorderLocation.Bottom:
                borderItem = Range.Style.Border.Bottom;
                break;
        }

        if (borderItem != null)
        {
            style = ExcelStyleToOperatorBorderStyle(borderItem.Style);
            indexed = borderItem.Color.Indexed;
            rgb = borderItem.Color.Rgb;
            var themeColor = borderItem.Color.Theme;
            if (themeColor != null)
            {
                theme = (int)themeColor;
            }
        }

        return new ExcelBorderState
        {
            BorderStyle = style,
            IndexedColor = indexed,
            Rgb = rgb,
            ThemeColor = theme
        };
    }


    public IExcelRangeOperator SetRangeStyleExceptFontStyle(RangeStyleOptions styleOptions)
    {
        Range.Style.HorizontalAlignment = OperatorHorizontalAlignmentToExcelAlignment(styleOptions.HAlign);
        Range.Style.VerticalAlignment = OperatorVerticalAlignmentToExcelAlignment(styleOptions.VAlign);

        if (styleOptions.BorderPosition != BorderPosition.None)
        {
            SetRangeBorderStyle(styleOptions.BorderPosition, styleOptions.BorderStyle);
        }

        if (styleOptions.InteriorColor != ExcelStyleColor.Empty &&
            styleOptions.InteriorColor != ExcelStyleColors.White)
        {
            Range.Style.Fill.SetBackground(styleOptions.InteriorColor.ToColor());
        }

        if (styleOptions.WrapText)
        {
            Range.Style.WrapText = true;
        }

        if (!string.IsNullOrEmpty(styleOptions.NumberFormatLocal))
        {
            Range.Style.Numberformat.Format = styleOptions.NumberFormatLocal;
        }

        return this;
    }


    private ExcelRangeOperator SetRangeStyle(RangeStyleOptions styleOptions)
    {
        SetRangeFontStyle(styleOptions.Font);
        SetRangeStyleExceptFontStyle(styleOptions);
        return this;
    }


    public IExcelRangeOperator AppendRichText(string text, FontStyleOptions fontStyleOptionsParam)
    {
        var richText = Range.RichText.Add(text);

        if (fontStyleOptionsParam.FontSize > 0)
        {
            richText.Size = fontStyleOptionsParam.FontSize;
        }

        if (!string.IsNullOrEmpty(fontStyleOptionsParam.FontFamily))
        {
            richText.FontName = fontStyleOptionsParam.FontFamily;
        }

        if (fontStyleOptionsParam.FontColor != ExcelStyleColor.Empty)
        {
            richText.Color = fontStyleOptionsParam.FontColor.ToColor();
        }

        if ((fontStyleOptionsParam.FontType & FontType.Bold) > 0)
        {
            richText.Bold = true;
        }

        if ((fontStyleOptionsParam.FontType & FontType.Italic) > 0)
        {
            richText.Italic = true;
        }

        if ((fontStyleOptionsParam.FontType & FontType.Underline) > 0)
        {
            richText.UnderLine = true;
        }

        return this;
    }

    public bool IsEmpty()
    {
        for (var row = 0; row < Range.Rows; row++)
        {
            for (var col = 0; col < Range.Columns; col++)
            {
                if (!string.IsNullOrEmpty(Range.Worksheet.Cells[StartRow + row, StartColumn + col].GetCellValue<string>()?.Trim()))
                {
                    return false;
                }
            }
        }

        return true;
    }

    public void Merge()
    {
        Range.Merge = true;
    }

    public IExcelRangeOperator SetHyperlink(IExcelRangeOperator target)
    {
        string address;

        if (string.IsNullOrEmpty(target.Worksheet.Name) || target.Worksheet.Name.Equals(Range.Worksheet.Name))
        {
            address = target.Address;
        }
        else
        {
            address = target.FullAddress;
        }

        Range.SetHyperlink(new ExcelHyperLink(address, null));
        SetRangeFontStyle(Worksheet.Workbook.HyperlinkStyleOptions);
        
        return this;
    }

    public IExcelRangeOperator SetStyle(RangeStyleOptions styleOptionsParam)
    {
        return SetRangeStyle(styleOptionsParam);
    }

    public IExcelRangeOperator Offset(int rowOffset, int columnOffset)
    {
        return new ExcelRangeOperator(Worksheet, Range.Offset(rowOffset, columnOffset));
    }

    public IExcelRangeOperator SetListValidation(string list)
    {
        var validation = Range.DataValidation.AddListDataValidation();
        validation.Formula.ExcelFormula = list;
        return this;
    }

    public IExcelRangeOperator SetIntegerLessEqualValidation(int upperBound)
    {
        var validation = Range.DataValidation.AddIntegerDataValidation();
        validation.Operator = OfficeOpenXml.DataValidation.ExcelDataValidationOperator.lessThanOrEqual;
        validation.Formula.Value = upperBound;
        return this;
    }

    public IExcelRangeOperator SetIntegerGreaterEqualValidation(int lowerBound)
    {
        var validation = Range.DataValidation.AddIntegerDataValidation();
        validation.Operator = OfficeOpenXml.DataValidation.ExcelDataValidationOperator.greaterThanOrEqual;
        validation.Formula.Value = lowerBound;
        return this;
    }


    public IExcelRangeOperator SetIntegerBetweenValidation(int lowerBound, int upperBound)
    {
        var validation = Range.DataValidation.AddIntegerDataValidation();
        validation.Operator = OfficeOpenXml.DataValidation.ExcelDataValidationOperator.between;
        validation.Formula.Value = lowerBound;
        validation.Formula2.Value = upperBound;
        return this;
    }


    public string? Comment
    {
        get => Range.Comment.Text;
        set
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            Range.Comment.Text = value;
        }
    }

}